Programming and management of experiments in oTree
An Introduction
Design
Smart design
16 animali, Enzo Mari, 1957
Computerized experiments
Advantages of computerized experiments
- Computerized experiments present several advantages over paper&pencil experiments
- Live interaction
- Dynamic interfaces
- Codified data
- Wide audience
- Web-based experiments
- Prolific, Mechanical Turk
- Web-based experiments
- Specialized software are available
- z-Tree (Fischbacher 2007) is the de-facto standard for lab experiments
- Runs locally on Win OS
- See my online lecture notes if interested
- oTree (Chen, Schonger, and Wickens 2016) is the current choice of most
- Platform-independent, web-based
- z-Tree (Fischbacher 2007) is the de-facto standard for lab experiments
- Here, we focus on oTree
About oTree
- oTree is a framework based on Python to run controlled experiments
- Games
- e.g., Public Goods Games (PGG)
- Individual decision making
- e.g., Risk elicitation tasks
- Surveys and tests
- e.g., Raven test
- Games
- Support by the community
- oTree is open-source,
- Licensed under an adaptation of the MIT license.
- Cite it in the paper when you use it
- Licensed under an adaptation of the MIT license.
Code
- Programming language of oTree is Python
- Popular object-oriented programming language
- Developed in the early 90’s by Guido Van Rossum
- OTree’s user interface is based on HTML5
- Supported by modern browsers and rich in functionalities
- Can be enriched with
- css
- javascript
- bootstrap
- …
- Can be enriched with
- Supported by modern browsers and rich in functionalities
- All the components of oTree are free and open-source
oTree’s architecture
- The basic setup consists in
- An app (experiment) written within oTree
- A server computer
- Cloud server, local PC …
- Subjects’ devices with a browser
- PC, Laptop, Tablet, Mobile Phone …
- oTree creates a session on the server and generates links for all participants
- Participants click on the links and are sent to a personal page
- They submit their answers, which are collected by the server
- The experimenter can check the progress on the server
oTree app
Object hierarchy
- oTree is based on a hierarchy of objects
- Each object contains information about the experiment
- A session is a series of subsessions
- A subsession contains multiple groups
- A group contains multiple players
- Each player proceeds through multiple pages
- A group contains multiple players
- A subsession contains multiple groups
- Example:
- 18 players take part in an experimental session made of 5 rounds and are matched in groups of 3 players. In each round, 2 pages are shown.
- 5 subsessions
- 6 groups
- 18 players
- 18 players take part in an experimental session made of 5 rounds and are matched in groups of 3 players. In each round, 2 pages are shown.
Object hierarchy (ii)
- You can access any higher-up object from a lower object
- e.g., Player can access the Group information
- but Group cannot access the Player information
- e.g., Player can access the Group information
Your first app
Create the projects folder
- Choose a convenient location
- e.g., inside DOCUMENTS
- Create the projects folder with the following terminal command
- Your appes will be developed here
otree startproject oTree_proj- You will be asked: Include sample games? (y or n):
- I suggest y
- Now move into your folder
cd oTree_proj- In the folder you will find some demo apps and other files like setting.py
- The apps in setting.py are contained in SESSION_CONFIGS = [] and can be run locally with the command
otree devserverCreate an app
- To create an app named hello_world move to the oTree folder
cd oTree- and create the app
otree startapp hello_world- Move to the folder my_first_app
- You will find the following files
- Python
- _init_.py
- HTML
- MyPage.html
- Results.html
- Python
- You will find the following files
_init_.py
- Contains Python classes (see introductory tutorial)
- Can be ideally divided into two parts
- models
- refers to main oTree’s entities
- Constants
- Subsession
- Group
- Player
- A model is basically a database
- Specify columns and their nature
- Integers, strings, …
- refers to main oTree’s entities
- pages
- refers to “screens” seen by participants
- models
- In older versions of oTree (<5) you could actually find models.py and pages.py in place of init
_init_.py: Content
# Import all necessary oTree modules and functions
from otree.api import *
# Documentation string describing what this app does
doc = """
Your app description
"""
#---------------------------------
# CONSTANTS
#---------------------------------
class C(BaseConstants):
# The URL name that will appear in the browser address bar
NAME_IN_URL = 'hello_world'
# Number of players per group (None means all players in one group)
PLAYERS_PER_GROUP = None
# Number of rounds this app will run
NUM_ROUNDS = 1
#---------------------------------
# MODELS
#---------------------------------
# Subsession: Contains all groups for one round of the experiment
class Subsession(BaseSubsession):
pass # No custom fields or methods defined yet
# Group: Contains multiple players who interact with each other
class Group(BaseGroup):
pass # No custom fields or methods defined yet
# Player: Individual participant with their data and decisions
class Player(BasePlayer):
pass # No custom fields or methods defined yet
#---------------------------------
# PAGES
#---------------------------------
# Main page where participants make decisions or view information
class MyPage(Page):
pass # No custom form fields or methods defined yet
# Wait page to synchronize all players before showing results
class ResultsWaitPage(WaitPage):
pass # Standard wait page with no customization
# Results page to show outcomes to participants
class Results(Page):
pass # No custom content defined yet
#---------------------------------
# ORDER OF PAGES
#---------------------------------
# Defines the sequence of pages participants will see
page_sequence = [MyPage, ResultsWaitPage, Results]Templates
<!-- Define the title block that will appear in the browser tab and page header -->
{{ block title }}
Page title
{{ endblock }}
<!-- Define the main content block that contains the page body -->
{{ block content }}
<!-- Automatically display all form fields defined in the Page class -->
{{ formfields }}
<!-- Display the standard "Next" button to proceed to the next page -->
{{ next_button }}
{{ endblock }}- These are the pages that are displayed to participants
- html files that contain informations and forms
- forms are used to collect data
- A default MyPage.html is created
- html files that contain informations and forms
- The HTML can rely on “fancy” stuff
- Javascript
- Bootstrap framework
- CSS
- …
Modify “MyPage.html”
Add the following title “Hello World!”
Add a smiley 😜
<!-- Set a custom title for this page -->
{{ block title }}
Hello World!
{{ endblock }}
<!-- Define the main content area -->
{{ block content }}
<!-- Display any form fields defined in the Page class -->
{{ formfields }}
<!-- Show the standard Next button -->
{{ next_button }}
<!-- Add custom HTML content: a large smiley emoji (Unicode character) -->
<p style="font-size:200px">😜</p>
<!-- Note: This duplicates formfields and next_button - usually not needed -->
{{ formfields }}
{{ next_button }}
{{ endblock }}Forms
Submitting information
- Each page in oTree can contain a form
- The player fills with some value and then submits it
- Cardinal values
- Integer, Float
- Ordinal values
- Integer, Categorical
- Text
- Strings of numbers and letters
- Cardinal values
- The player fills with some value and then submits it
- Several formats to collect values
- Open fields
- Buttons
- Radio buttons
- Dropdown lists
- …
Basic structure of forms
- First create fields in
__init__.py, models section - As an example, if you want to collect name and age
class Player(BasePlayer):
name = models.StringField(label="Your name:")
age = models.IntegerField(label="Your age:")- Then in *__init__py*, pages section, you will create a class
class Anag(Page):
form_model = 'player'
form_fields = ['name', 'age'] # saved as player.name, player.age- Finally, in a template Anag.html the form will be displayed with
{{ formfields }}- To display the forms separately
{{ formfields player.age }}Input formats
Examples
- In the following examples the participant receives a random number
- The participant is asked to report features of the number
- Several input interfaces are presented
- Radio
- Button
- Checker
- Dropdown
- Radio Sequence
- Text
- Value
- Tabular
- Sliders
- Several input interfaces are presented
Radio
__init.py__(Models)
input_radio = models.CharField(
choices=['Odd', 'Even'],
widget=widgets.RadioSelect)__init.py__(Pages)
class Radio(Page):
form_model = 'player'
form_fields = ['input_radio']- Template
{{ block content }}
{{ formfield player.input_radio label="The number is:"}}
{{ next_button }}
{{ endblock }}Checker
__init.py__(Models)
class Player(BasePlayer):
input_checker = models.CharField(
choices=[
["Even", 'Yes'],
["Odd", 'No']
],
widget=widgets.RadioSelectHorizontal,
label="Is the number even?")__init.py__(Pages)
class Checker(Page):
form_model = 'player'
form_fields = ['input_checker']- Template
{{ block content }}
{{ formfield player.input_checker }}
{{ next_button }}
{{ endblock }}Dropdown
__init.py__(Models)
class Player(BasePlayer):
input_dropdown = models.IntegerField(
choices=[1, 2, 3, 4, 5, 6, 7, 8, 9])__init.py__(Pages)
class Dropdown(Page):
form_model = 'player'
form_fields = ['input_dropdown']- Template
{{ block content }}
{{ formfield player.input_dropdown label="The number is:"}}
{{ next_button }}
{{ endblock }}Radiosequence
__init.py__(Models)
class Player(BasePlayer):
input_radiosequence = models.IntegerField(
choices=[1, 2, 3, 4, 5, 6, 7, 8, 9],
widget=widgets.RadioSelectHorizontal
)__init.py__(Pages)
class RadioSequence(Page):
form_model = 'player'
form_fields = ['input_radiosequence']- Template
{{ block content }}
{{ formfield player.input_radiosequence label="The number is:"}}
{{ next_button }}
{{ endblock }}Text
__init.py__(Models)
class Player(BasePlayer):
input_text = models.CharField()__init.py__(Pages)
class Text(Page):
form_model = 'player'
form_fields = ['input_text']- Template
{{ block content }}
{{ formfield player.input_text label="The number is:"}}
{{ next_button }}
{{ endblock }}Value
__init.py__(Models)
class Player(BasePlayer):
input_value = models.IntegerField(min=1, max=9)__init.py__(Pages)
class Value(Page):
form_model = 'player'
form_fields = ['input_value']- Template
{{ block content }}
{{ formfield player.input_value label="The number is:"}}
{{ next_button }}
{{ endblock }}Tabular
__init.py__(Models)
class Player(BasePlayer):
tab_1 = models.BooleanField(blank=True)
tab_2 = models.BooleanField(blank=True)
tab_3 = models.BooleanField(blank=True)
tab_4 = models.BooleanField(blank=True)
tab_5 = models.BooleanField(blank=True)
tab_6 = models.BooleanField(blank=True)
tab_7 = models.BooleanField(blank=True)
tab_8 = models.BooleanField(blank=True)
tab_9 = models.BooleanField(blank=True)__init.py__(Pages)
class Tabular(Page):
form_model = 'player'
form_fields = ['tab_1','tab_2','tab_3','tab_4','tab_5','tab_6','tab_7','tab_8','tab_9']- Template
{{ block content }}
<table class="table table-bordered">
<tbody>
<tr>
<td><button name="tab_1" value="True" class="btn btn-outline-primary" btn-lg"> 1 </button></td>
<td> <button name="tab_2" value="True" class="btn btn-outline-primary" btn-lg">2</button></td>
<td> <button name="tab_3" value="True" class="btn btn-outline-primary" btn-lg">3</button></td>
</tr>
<tr>
<td><button name="tab_4" value="True" class="btn btn-outline-primary" btn-lg">4</button></td>
<td><button name="tab_5" value="True" class="btn btn-outline-primary" btn-lg">5</button></td>
<td><button name="tab_6" value="True" class="btn btn-outline-primary" btn-lg">6</button></td>
</tr>
<tr>
<td ><button name="tab_7" value="True" class="btn btn-outline-primary" btn-lg">7</button></td>
<td><button name="tab_8" value="True" class="btn btn-outline-primary" btn-lg">8</button></td>
<td><button name="tab_9" value="True" class="btn btn-outline-primary" btn-lg">9</button></td>
</tr>
</tbody>
</table>
{{ endblock }}Slider (simple)
__init.py__(Models)
input_slider = models.IntegerField(min=1, max=9)__init.py__(Pages)
class Slider(Page):
form_model = 'player'
form_fields = ['input_slider']- Template
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Disagree</span>
</div>
<input type="range" name="input_slider" min="1" max="9" step="1">
<div class="input-group-append">
<span class="input-group-text">Agree</span>
</div>
</div>
{{ form.input_slider.errors }}Sliders (boosted)
- Relies on css styling + javascript code
• Hidden Initial State: No visible thumb until first interaction
• Click-to-Activate: Pointer appears on click, value calculated from click position
• Real-Time Display: Shows selected value (0-100) with continuous updates
• Cross-Platform: Works on desktop and mobile devices
• Enhanced Feedback: Smooth transitions and visual styling changes
- Template
{{ block title }}
Slider Plus
{{ endblock }}
{{ block content }}
<div class="slider-container">
<!-- Label for accessibility and instructions -->
<label for="input_slider">Please select a value between 0 and 100:</label>
<div class="slider-wrapper">
<div class="slider-with-labels">
<!-- Left label for minimum value -->
<span class="slider-label-left">0</span>
<!-- The actual slider input, styled and controlled by JS/CSS -->
<input type="range"
id="input_slider"
name="input_slider"
min="0"
max="100"
step="1"
class="custom-slider">
<!-- Right label for maximum value -->
<span class="slider-label-right">100</span>
</div>
<!-- Area to display the selected value or instructions -->
<div id="slider-value" class="slider-value">Click on the slider to select a value</div>
</div>
</div>
<!-- Error display for form validation (commented out, can be enabled if needed) -->
<!-- {{ form.input_slider.errors }} -->
<br>
<!-- Next button container, hidden by default and shown only after a value is selected -->
<div id="next-button-container" style="display: none;">
{{ next_button }}
</div>
{{ endblock }}Sliders (boosted) - CSS and JS
- The slider relies on a custom code in the template
- The code is written in the
scriptsblock
- The code is written in the
{{ block scripts }}
{{ block scripts }}
<script>
// Wait for the DOM to be fully loaded before running the script
document.addEventListener('DOMContentLoaded', function() {
// Get references to slider, value display, and next button container
const slider = document.getElementById('input_slider');
const valueDisplay = document.getElementById('slider-value');
const nextButtonContainer = document.getElementById('next-button-container');
let hasBeenClicked = false; // Track if slider has been activated
// Hide the slider value initially (no value selected)
slider.value = '';
// Update the value display and next button visibility
function updateValueDisplay(value) {
if (value !== '') {
valueDisplay.textContent = `Selected value: ${value}`;
valueDisplay.classList.add('has-value');
// Show the next button with animation
nextButtonContainer.style.display = 'block';
setTimeout(() => {
nextButtonContainer.classList.add('show');
}, 10);
} else {
valueDisplay.textContent = 'Click on the slider to select a value';
valueDisplay.classList.remove('has-value');
// Hide the next button
nextButtonContainer.classList.remove('show');
nextButtonContainer.style.display = 'none';
}
}
// Handle the first mouse click on the slider
function handleFirstInteraction(event) {
if (!hasBeenClicked) {
hasBeenClicked = true;
slider.classList.add('activated'); // Show the thumb
// Calculate where the user clicked and set slider value accordingly
const rect = slider.getBoundingClientRect();
const percent = (event.clientX - rect.left) / rect.width;
const value = Math.round(percent * 100);
const clampedValue = Math.max(0, Math.min(100, value));
slider.value = clampedValue;
updateValueDisplay(clampedValue);
}
}
// Handle the first touch on mobile devices
function handleFirstTouch(event) {
if (!hasBeenClicked) {
hasBeenClicked = true;
slider.classList.add('activated');
// Use touch coordinates to set slider value
const touch = event.touches[0];
const rect = slider.getBoundingClientRect();
const percent = (touch.clientX - rect.left) / rect.width;
const value = Math.round(percent * 100);
const clampedValue = Math.max(0, Math.min(100, value));
slider.value = clampedValue;
updateValueDisplay(clampedValue);
}
}
// Listen for first interaction (mouse or touch)
slider.addEventListener('mousedown', handleFirstInteraction);
slider.addEventListener('touchstart', handleFirstTouch);
// After activation, update value display on slider movement
slider.addEventListener('input', function() {
if (hasBeenClicked) {
updateValueDisplay(this.value);
}
});
// Prevent slider from working before activation
slider.addEventListener('input', function(event) {
if (!hasBeenClicked) {
event.preventDefault();
this.value = '';
}
});
// Initialize the display in the "not yet clicked" state
updateValueDisplay('');
});
</script>
{{ endblock }}- The slider relies on a custom CSS code in the template
- The code is written in the
stylesblock
- The code is written in the
{{ block styles }}
<style>
/* Main container for the entire slider component */
.slider-container {
margin: 20px 0; /* Adds vertical spacing above and below */
text-align: center; /* Centers all content horizontally */
}
/* Wrapper for the slider and its associated elements */
.slider-wrapper {
margin: 20px 0; /* Adds vertical spacing */
position: relative; /* Allows absolute positioning of children if needed */
}
/* Flex container for slider and its labels */
.slider-with-labels {
display: flex; /* Horizontal layout for children */
align-items: center; /* Vertically center items */
gap: 10px; /* Space between slider and labels */
margin: 10px 0; /* Vertical spacing */
}
/* Styling for the "0" and "100" labels */
.slider-label-left,
.slider-label-right {
font-weight: bold; /* Bold text */
font-size: 16px; /* Medium size */
color: #666; /* Gray color */
min-width: 30px; /* Minimum width for alignment */
text-align: center; /* Center text */
}
/* Main slider input styling */
.custom-slider {
flex: 1; /* Expands to fill space between labels */
height: 8px; /* Thin track */
border-radius: 5px; /* Rounded corners */
background: #ddd; /* Light gray track */
outline: none; /* No focus outline */
opacity: 0.7; /* Slightly transparent */
transition: opacity 0.2s; /* Smooth opacity change */
-webkit-appearance: none; /* Remove default browser styling (WebKit) */
appearance: none; /* Remove default browser styling (standard) */
}
/* Slider becomes fully opaque on hover */
.custom-slider:hover {
opacity: 1;
}
/* Hide the slider thumb initially (WebKit browsers) */
.custom-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0;
height: 0;
background: transparent;
cursor: pointer;
transition: all 0.3s ease;
}
/* Hide the slider thumb initially (Firefox) */
.custom-slider::-moz-range-thumb {
width: 0;
height: 0;
background: transparent;
cursor: pointer;
border: none;
transition: all 0.3s ease;
}
/* Show the thumb after activation (WebKit browsers) */
.custom-slider.activated::-webkit-slider-thumb {
width: 20px;
height: 20px;
background: #04AA6D; /* Green thumb */
border-radius: 50%; /* Circular */
box-shadow: 0 0 4px rgba(0,0,0,0.3); /* Subtle shadow */
}
/* Show the thumb after activation (Firefox) */
.custom-slider.activated::-moz-range-thumb {
width: 20px;
height: 20px;
background: #04AA6D;
border-radius: 50%;
box-shadow: 0 0 4px rgba(0,0,0,0.3);
border: none;
}
/* Style the slider track (WebKit browsers) */
.custom-slider::-webkit-slider-track {
width: 100%;
height: 8px;
cursor: pointer;
background: #ddd;
border-radius: 5px;
}
/* Style the slider track (Firefox) */
.custom-slider::-moz-range-track {
width: 100%;
height: 8px;
cursor: pointer;
background: #ddd;
border-radius: 5px;
border: none;
}
/* Styling for the value display box */
.slider-value {
margin-top: 15px;
font-size: 18px;
font-weight: bold;
color: #333;
min-height: 25px;
padding: 10px;
background-color: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
display: inline-block;
min-width: 200px;
}
/* Highlight value display when a value is selected */
.slider-value.has-value {
color: #04AA6D;
border-color: #04AA6D;
background-color: #f0fff4;
}
/* Container for the next button, initially hidden and faded out */
#next-button-container {
margin-top: 20px;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
/* Show the next button with fade-in */
#next-button-container.show {
opacity: 1;
}
</style>
{{ endblock }}–>
Steps to implement the survey
- Implement a simple survey to collect the age of course participants
- Develop the oTree code
__init__.py- templates
- Test our code locally
- Transfer our code to a server (running oTree)
- Send links to participants (you)
- Fill in the survey
- Collect outcomes and analyze them
__init__.py: models
from otree.api import *
author = 'MP'
doc = """
A simple app to collect the age of respondent
"""
class C(BaseConstants):
NAME_IN_URL = 'my_first_survey'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 1
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
age = models.IntegerField(choices=range(18, 99, 1))- Create the field
agein class player- The input will be an integer spanning 18-99
__init__.py: pages
class CollectAge(Page):
form_model = 'player'
form_fields = ['age']
class Results(Page):
pass
page_sequence = [CollectAge, Results]- In page
CollectAgewe insert a form for class player- The field is
age- Must be the same name we used in models.py
ageand not, as an example,Age
- Must be the same name we used in models.py
- The field is
- In page
Resultswe give a feedbackyour_agewill be printed on the screen
page_sequencedefines the sequence of your pages- All your pages are a class that must be defined here
Develop the oTree code: templates
- Templates are the .html files
- CollectAge
{{ block title }}
Insert your age here
{{ endblock }}
{{ block content }}
{{ formfield player.age }}
{{ next_button }}
{{ endblock }}{ formfield player.age }is where the information is input- Variable
agein the class player
- Variable
- Aesthetics elements
- Title
- Body
- Next button
Develop the oTree code: templates (ii)
- Results
{{ block title }}
Your age
{{ endblock }}
{{ block content }}
Your age is {{player.age}}. Thank you for answering!
{{ next_button }}
{{ endblock }}{ player.age }is passed gathered from class player- The right name must be put within
{ }brackets
- The right name must be put within
Test our code locally
See also the video tutorial!
Move to the folder in which your oTree is installed
- Usually
cd ~/oTree - Add your new app to the `settings.py file
- Usually
SESSION_CONFIGS = [
dict(
name='my_first_survey',
display_name='my_first_survey',
num_demo_participants=4,
app_sequence=['my_first_survey'],
)
]- In
app_sequencethe exact name of the apps should be given - Give command
otree devserverTest our code locally (ii)
- Open a browser and insert
http://localhost:8000/
- Click on the name of your app
- Click on the session-wide link and try it
Appendix
Assignment 1
- Consider the class Pokemon (see below)
- Print the name of p_1
- Print the ability of p_1
- Create a new pokemon (instance) named Charizard
- Charizard: Height: 5’ 07”; Weight: 199.5 lbs; Category: Flame
- Check whether Charizard is taller than Bulbasaur
Assignment
- Create the
my_first_surveyapp- Add the field
gender- Categorical variable with values:
Male,Female,Non-binary,Prefer not to answer
- Categorical variable with values:
- Collect gender both with
- Radio buttons
- Dropdown list
- Add the field
Python Basics
- A very short introduction to Python
- Based on Learn X in Y minutes, where X=Python Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
- and on Ascher and Lutz (1999)
- You can test the code in Google Colab
Basics: Numbers and logical operators
#####################################################
# Numbers and logical operators
####################################################
# Math is what you would expect
1 + 1 # => 2
8 - 1 # => 7
10 * 2 # => 20
35 / 5 # => 7.0
# Enforce precedence with parentheses
1 + 3 * 2 # => 7
(1 + 3) * 2 # => 8
# Boolean Operators
## Note "and" and "or" are case-sensitive
True and False # => False
False or True # => True
## True and False are actually 1 and 0 but with different keywords
True + True # => 2
True * 8 # => 8
False - 5 # => -5
# Comparisons
## Equality is ==
1 == 1 # => True
2 == 1 # => False
## Inequality is !=
1 != 1 # => False
2 != 1 # => True
## More comparisons
1 < 10 # => True
1 > 10 # => False
2 <= 2 # => True
2 >= 2 # => TrueBasics: Strings and variables
- Strings are an ordered collection of characters
# Strings are created with " or '
"This is a string."
'This is also a string.'
# Strings can be added too! But try not to do this.
"Hello " + "world!" # => "Hello world!"
# You can also format using f-strings or formatted string literals (in Python 3.6+)
name = "Reiko"
f"{name} is {len(name)} characters long." # => "Reiko is 5 characters long."
# Strings can be sliced and indexed
name[0] => "R"
name[-2] => "k"
# There are no declarations, only assignments.
# Convention is to use lower_case_with_underscores
some_var = 5
some_var # => 5Basics: Lists and tuples
- Lists are ordered collections of arbitrary items
- Can contain numbers, strings, and other lists
- Accessed by offset
- As an example, all players in a group
# empty list
li = []
# Prefilled list
other_li = [4, 5, 6]
# Add stuff to the end of a list with append
other_li.append(7) # => li is now [4, 5, 6, 7]
# Remove from the end with pop
other_li.pop() # => 7 li is now [4, 5, 6]
# Access a list like you would any array
other_li [0] # => 4
# Look at the last element
other_li [-1] # => 6
# You can look at ranges with slice syntax.
# The start index is included, the end index is not
# (It's a closed/open range for you mathy types.)
other_li [1:3] # Return list from index 1 to 3 => [5, 6]
# Remove arbitrary elements from a list with "del"
del other_li[2] # li is now [4, 5]
# Insert an element at a specific index
other_li.insert(1, 2) # li is now [4, 2, 5] again
# Get the index of the first item found matching the argument
other_li.index(2) # => 1
# Check for existence in a list with "in"
1 in other_li # => False
# Examine the length with "len()"
len(other_li) # => 3
# Tuples are like lists but are immutable.
tup = (1, 2, 3)
tup[0] # => 1Basics: Dictionaries
- Useful way to collect and organize information
- As an example, payoffs of players
# Dictionaries store mappings from keys to values
empty_dict = {}
# Here is a prefilled dictionary
filled_dict = {"one": 1, "two": 2, "three": 3}
# Look up values with []
filled_dict["one"] # => 1
# Get all keys as an iterable with "keys()". We need to wrap the call in list()
# to turn it into a list. Order of insertion is preserved (in Python >=3.7)
list(filled_dict.keys()) # => ["one", "two", "three"]
# Get all values as an iterable with "values()". Once again we need to wrap it
# in list() to get it out of the iterable.
list(filled_dict.values()) # => [1, 2, 3]
# Check for existence of keys in a dictionary with "in"
"one" in filled_dict # => True
1 in filled_dict # => False
# Adding to a dictionary
filled_dict.update({"four":4}) # => {"one": 1, "two": 2, "three": 3, "four": 4}
filled_dict["four"] = 4 # another way to add to dict
# Remove keys from a dictionary with del
del filled_dict["one"] # Removes the key "one" from filled dictBasics: Dictionaries in oTree
- File setting.py in oTree controls the apps that are run
- Apps are dictionaries contained in a list SESSION_CONFIGS
- keys: name, display_name, num_demo_participants, app_sequence, …
- Apps are dictionaries contained in a list SESSION_CONFIGS
SESSION_CONFIGS = [
dict(
name='my_first_survey',
display_name='my_first_survey',
num_demo_participants=4,
app_sequence=['my_first_survey'],
),
dict(
name='self',
display_name='importance_of_self',
num_demo_participants=4,
app_sequence=['what_is_self'],
)
]
SESSION_CONFIGS[0].keys()- dict_keys([‘name’, ‘display_name’, ‘num_demo_participants’, ‘app_sequence’])
SESSION_CONFIGS[0].values()- dict_values([‘my_first_survey’, ‘my_first_survey’, 4, [‘my_first_survey’]])
Basics: Control Flow and Iteration
- Many times you need to iterate through variables
- As an example, collect all choice sof participants in a group
# Let's just make a variable
some_var = 5
# Here is an if statement. Indentation is significant in Python!
# Convention is to use four spaces, not tabs.
# This prints "some_var is smaller than 10"
# if conditions
if some_var > 10:
print("some_var is totally bigger than 10.")
elif some_var < 10: # This elif clause is optional.
print("some_var is smaller than 10.")
else: # This is optional too.
print("some_var is indeed 10.")
# loops
for i in range(4):
print(i)
# =>0
# =>1
# =>2
# =>3
for animal in ["dog", "cat", "mouse"]:
# You can use format() to interpolate formatted strings
print("{} is a mammal".format(animal))
# =>dog is a mammal
# =>cat is a mammal
# =>mouse is a mammal
# We can use list comprehensions to loop or filter
numbers = [3, 4, 5, 6, 7]
[x for x in numbers if x > 5] # => [6, 7]- Indenting is important!
Basics: Functions
- Functions are a device that groups a bunch of statements
# Use "def" to create new functions
def add(x, y):
print("x is {} and y is {}".format(x, y))
return x + y # Return values with a return statement
# Calling functions with parameters
add(5, 6) # => prints out "x is 5 and y is 6" and returns 11Basics: Classes
- Classes are main object-oriented-programming (OOP) in Python
- Class objects provide default behavior
- The class statement creates a class object and assigns it a name
- Assignments inside class statements make class attributes
- Class attributes export object state and behavior
- def statements inside class generate a method
- Instance objects are generated from classes
- Calling a class object makes a new instance object
- Each instance object inherits class attributes and gets its own namespace
- Assignments to self in methods make per-instance attributes
- self refers to the instance being processed
- Class objects provide default behavior
Classes: An Example
- Pokemon is a class with some properties
- Height
- Weight
- Category
- Two instances of the class →
Charmander:
Height: 2’ 00”;
Weight 18.7 lbs;
Category: Lizard
Bulbasaur:
Height: 2’ 04”;
Weight: 15.2 lbs;
Category: Seed
Classes: An Example (ii)
class Pokemon:
nature = "Pokemon"# this property is shared by all instances
def __init__(self, name, height, weight, category):
# Assign the argument to the instance's name attribute
self.name = name
self.height = height
self.weight = weight
self.category = category
self.comment=[]
# to add a comment
def add_comment(self, comment):
self.comment.append(comment)
# convert height and weight into metric
def convert_metric(self):
# conversion rates
feet_conv_cm=30.5
inch_conv_cm=2.54
lbs_conv_kg=0.453592
self.height_cm = int(self.height.split("'")[0])*feet_conv_cm+int(self.height.split("'")[1])*inch_conv_cm
self.weight_kg = self.weight*lbs_conv_kg
# print the result
print("Height (cm): %f, Weight (Kg): %f" %(self.height_cm, self.weight_kg))Classes: An Example (iii)
# Create two instances of the class
p_1 = Pokemon("Charmander","2' 5''", 18.7, "Lizard")
p_2 = Pokemon("Bulbasaur","2' 04''", 15.2, "Seed")
# Add a comment
p_1.add_comment("Its ability is Blaze")
p_2.add_comment("Its ability is Overgrow")
# Convert their measures in metric system
p_1.convert_metric()
p_2.convert_metric()
# Who is taller?
if p_1.height>p_2.height:
print(p_1.name + " is taller")
elif p_1.height<p_2.height:
print(p_2.name + " is taller")
else:
print(p_2.name + " and" + p_1.name + "are equally taller")References
References
Ascher, David, and Mark Lutz. 1999. Learning Python. O’Reilly.
Chen, Daniel L, Martin Schonger, and Chris Wickens. 2016. “oTree—an Open-Source Platform for Laboratory, Online, and Field Experiments.” Journal of Behavioral and Experimental Finance 9: 88–97.
Fischbacher, Urs. 2007. “Z-Tree: Zurich Toolbox for Ready-Made Economic Experiments.” Experimental Economics 10 (2): 171–78.
Holzmeister, Felix. 2017. “oTree: Ready-Made Apps for Risk Preference Elicitation Methods.” Journal of Behavioral and Experimental Finance 16: 33–38.